
package JET.mygui;

import JET.mygui.interfaces.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import org.w3c.dom.*;

/**
 *
 * @author raos
 */
public abstract class TabsPane extends JPanel implements ComponentListener {

    ////////////////////////////////////////////////////////////////////////////
    //                       T A B   L I S T E N E R S                        //
    ////////////////////////////////////////////////////////////////////////////

    public interface TabListener {
        // When tab becomes selected in tabs pane this method is called by TabsPane
        void tabSelected(TabsPane tp);
        // When tab becomes focused in tabs pane this method is called by TabsPane
        void tabFocused(TabsPane tp);
    }

    public interface TabsPaneTab {
        /**
         * This is standard code for this interface implementation.
         * Copy paste it to your class implementing this interface.
         *
         *
         *
            ArrayList<TabListener> tabListeners = new ArrayList<TabListener>();

            public void addTabListener(TabListener l) {
                if (!tabListeners.contains(l))
                    tabListeners.add(l);
            }
            public void removeTabListener(TabListener l) {
                tabListeners.remove(l);
            }
            public void notifyTabSelected(TabsPane tp) {
                for(TabListener l : tabListeners)
                    l.tabSelected(tp);
            }
            public void notifyTabFocused(TabsPane tp) {
                for(TabListener l : tabListeners)
                    l.tabFocused(tp);
            }
         *
         */

        // Methods for user to make him able to register his tab selection listerners
        void addTabSelectionListener(TabListener l);
        void removeTabSelectionListener(TabListener l);
        
        // Method called by TabsPane after tab selection
        void notifyTabSelected(TabsPane tp);
        // Method called by TabsPane after tab selection
        void notifyTabFocused(TabsPane tp);
    }

    ////////////////////////////////////////////////////////////////////////////
    //                          T A B S   P A N E                             //
    ////////////////////////////////////////////////////////////////////////////

    protected ArrayList<JComponent> tabs = new ArrayList<JComponent>();
    protected TabsPaneHeader        header = new TabsPaneHeader();
    protected int                   actualTabIndex = -1;
    protected int                   headerHegiht = 19;
    protected boolean               stayAliveWhenEmpty = false;
    protected boolean               hasFocusFromTabs = false;
    
    // Focus listener that is being sowed on component that is added as tab to this tabs pane
    protected TabsPaneTabFocusListener tabsFocusListener;

    // List of tab panes focus listeners
    // Those listeners are notified when any of this tabs pane tab gets focused
    //protected ArrayList<TabsPaneMyFocusListener> tabsPaneMyFocusListeners;

    public TabsPane(String name) {
        getAccessibleContext().setAccessibleName(name);
        //setBackground(Color.WHITE);
        setLayout(null);
        addComponentListener(this);
        setHeader(createNewTabsPaneHeader());
        this.tabsFocusListener = createNewTabsPaneFocusListener();

        //tabsPaneMyFocusListeners = new ArrayList<TabsPaneMyFocusListener>();
    }

    protected TabsPaneHeader createNewTabsPaneHeader() {
        return new TabsPaneHeader();
    }

    protected TabsPaneTabFocusListener createNewTabsPaneFocusListener() {
        return new TabsPaneTabFocusListener(this);
    }

    protected void setHeader(TabsPaneHeader header) {
        super.add(header);
        header.addFocusListener(tabsFocusListener);
        header.setBounds(0, 0, getWidth(), headerHegiht);
    }

    /**
     * States if this tabs pane has tabs or not.
     * @return True - if has tabs, False - if doesn't have tabs.
     */
    public boolean isEmpty() {
        return tabs.isEmpty();
    }

    /**
     * States if this tabs pane contains specified component.
     */
    public boolean contains(Component comp) {
        return tabs.contains(comp);
    }

    /**
     * States if this tabs pane should be removed form its parent, when
     * is becomes empty (has no tabs).
     *
     * @return True - if this tab shouldn't be removed when empty,
     *         False - otherwise.
     */
    public boolean isStayAliveWhenEmpty() {
        return stayAliveWhenEmpty;
    }

    public void setStayAliveWhenEmpty(boolean value) {
        this.stayAliveWhenEmpty = value;
    }

    /**
     * Adds new tab at the end of all tabs list (maximum to the right).
     *
     * @param comp Component to be added as a new tab.
     * @return Component added as a new tab.
     */
    @Override
    public Component add(Component comp) {
        MyGuiHelper.sowFocusListenerOnComp(comp, tabsFocusListener);
        tabs.add((JComponent) comp);
        displayTab(tabs.size()-1);
        
        return comp;
    }

    /**
     * Adds comp into tabs pane at specified location in tabs pane.
     * Locations indexes grow from left to right.
     *
     * @param comp Component that is being added
     * @param index Tab location from left to right
     * @return Added component.
     */
    @Override
    public Component add(Component comp, int index) {
        MyGuiHelper.sowFocusListenerOnComp(comp, tabsFocusListener);
        
        if (index<0) index = 0;
        if (index>tabs.size()) index = tabs.size();

        tabs.add(index, (JComponent) comp);
        displayTab(index);

        return comp;
    }

    /**
     * Removes specified tab (JComponent) from this tabs pane and
     * displays last tab from tabs list.
     * 
     * @param comp
     */
    @Override
    public void remove(Component comp) {
        if (!isEmpty()) {
            MyGuiHelper.cropFocusListenerFromComp(comp, tabsFocusListener);
            tabs.remove((JComponent) comp);
            super.remove(comp);
            
            if (tabs.size()>0)
                displayTab(tabs.size()-1);
            else
                updateUI();
        }
    }

    public int getNextTabToDisplayIndex(int index) {
        if (index>0)
            return index-1;
        else
            return 0;
    }
    
    public void displayTab(int t) {
        if (isEmpty())
            return;
            
        if (t>=0 && t<tabs.size()) {
            int lastTabIndex = actualTabIndex;
            JComponent tab = tabs.get(t);
            removeAll();
                       
            actualTabIndex = t;
            super.add(tab);
            resizeActualComponent();
            setHeader(header);

            if (lastTabIndex!=actualTabIndex) {
                if (tab instanceof TabsPaneTab)
                    ((TabsPaneTab)tab).notifyTabSelected(this);
            }

            this.updateUI();
        }
    }

    public void displayTab(Component comp) {
        displayTab(getTabIndex(comp));
    }

    public JComponent getActualComponent() {
        if (isEmpty())
            return null;
        else
            return tabs.get(actualTabIndex);
    }

    public String getTabName(int index) {
        JComponent comp = tabs.get(index);
        return MyGuiHelper.getComponentAfterScrollPane(comp).getAccessibleContext().getAccessibleName();
//        if (comp instanceof JScrollPane) {
//            JScrollPane sp = (JScrollPane) comp;
//            return sp.getViewport().getComponent(0).getAccessibleContext().getAccessibleName();
//        }
//        else return comp.getAccessibleContext().getAccessibleName()==null? "Tab["+actualTabIndex+"]" :
//                    comp.getAccessibleContext().getAccessibleName();
    }

    /**
     * Returns index of specified tab/component.
     *
     * @param comp
     * @return
     */
    public int getTabIndex(Component comp) {
        return tabs.indexOf(comp);
    }

    public void resizeActualComponent() {
        JComponent comp = getActualComponent();
        if (comp!=null)
            comp.setBounds(1,headerHegiht+1, getWidth()-2, getHeight()-headerHegiht-2);
    }

    public JScrollPane getScrollPaneOfActualComp(Component comp) {
        Component iter = comp.getParent();

        while( iter!=null && !(iter instanceof JScrollPane))
            iter = iter.getParent();

        return (JScrollPane)iter;
    }


    ////////////////////////////////////////////////////////////////////////////
    //                            P A I N T I N G                             //
    ////////////////////////////////////////////////////////////////////////////

    /** Fills background with parent's color */
    protected void fillBackground(Graphics g) {
        Component parent = getParent();
        Color bgColor = parent==null? Color.WHITE : parent.getBackground();
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(new Color(127, 157, 185));
        g.drawLine(0, headerHegiht, 0, getHeight());
        g.drawLine(getWidth()-1, headerHegiht, getWidth()-1, getHeight());
        g.drawLine(0, getHeight()-1, getWidth()-1, getHeight()-1    );
        //g.drawRect(0, headerHegiht, getWidth()-1, getHeight()-headerHegiht-1);
    }

    public String toString(int level) {
        String str = "";
        for (int i=0; i<tabs.size(); i++)
            str += MyGuiHelper.appendSpaces((level+1)*4, getTabName(i) + ",\n");
        String ret = "TabsPane["+getAccessibleContext().getAccessibleName()+"]{\n"+str+ MyGuiHelper.appendSpaces((level)*4,"}");
        MyGuiHelper.appendSpaces(level*4, ret);
        return ret;
    }

    ////////////////////////////////////////////////////////////////////////////
    //       M Y   F O C U S   L I S T E N E R S   M A N A G E M  N T         //
    ////////////////////////////////////////////////////////////////////////////

    /*
    public void addTabsPaneMyFocusListener(TabsPaneMyFocusListener l) {
        if (!tabsPaneMyFocusListeners.contains(l))
            tabsPaneMyFocusListeners.add(l);
    }
    
    public void removeTabsPaneMyFocusListener(TabsPaneMyFocusListener l) {
        tabsPaneMyFocusListeners.remove(l);
    }
    */

    ////////////////////////////////////////////////////////////////////////////
    //                   C O M P O N E N T   L I S T E N E R                  //
    ////////////////////////////////////////////////////////////////////////////

    public void componentResized(ComponentEvent e) {
        header.setBounds(0,0, getWidth(), headerHegiht);
        resizeActualComponent();
        updateUI();
    }
    public void componentMoved(ComponentEvent e) {
    }
    public void componentShown(ComponentEvent e) {
    }
    public void componentHidden(ComponentEvent e) {
    }

    ////////////////////////////////////////////////////////////////////////////
    //            F O C U S   L I S T E N E R   C L A S S                     //
    ////////////////////////////////////////////////////////////////////////////

    protected class TabsPaneTabFocusListener implements FocusListener {

        TabsPane ownerTabsPane;

        protected TabsPaneTabFocusListener(TabsPane tp) {
            ownerTabsPane = tp;
        }
        public synchronized void focusGained(FocusEvent e) {
            boolean hadFocus = ownerTabsPane.hasFocusFromTabs;
            //System.out.println("TabsPane["+TabsPane.this.getAccessibleContext().getAccessibleName()+"].TabsPaneTabFocusListener.focusGained(), hadFocus: "+hadFocus);
            ownerTabsPane.hasFocusFromTabs = true;
            ownerTabsPane.repaint();

            // Notify tabs pane my focus listeners that some element of this tabs pane has focus
            /*
            if (!hadFocus)
                for(TabsPaneMyFocusListener l : TabsPane.this.tabsPaneMyFocusListeners)
                    l.focusGained(TabsPane.this, TabsPane.this.actualTabIndex);
            */

            if (!hadFocus) {
                Component tab = getActualComponent();
                if (tab instanceof TabsPaneTab)
                    ((TabsPaneTab)tab).notifyTabFocused(ownerTabsPane);
            }
        }

        public synchronized void focusLost(FocusEvent e) {
            ownerTabsPane.hasFocusFromTabs = false;
            ownerTabsPane.repaint();
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //                         X M L   S T U F F                              //
    ////////////////////////////////////////////////////////////////////////////

    public Element getXMLElement(Document doc) {
        Element root = doc.createElement("TabsPane");

        // TODO: maybe here should be real code for creation of what is inside of this tabs pane,
        // but for now i dont think raw TabsPane will be ever used
        return root;
    }

    ////////////////////////////////////////////////////////////////////////////
    //                      H E A D E R   C L A S S                           //
    ////////////////////////////////////////////////////////////////////////////

    protected  class TabsPaneHeader extends JPanel implements MouseListener, MouseMotionListener, MyGuiDnDReady {

        protected int mouseOverIndex = -1;
        
        public TabsPaneHeader() {
            super();
            addMouseMotionListener(this);
            addMouseListener(this);
            MyGuiManager.getInstance().registerDnDReady(this);
            setFocusable(true);
        }

        /**
         * Calculates which tab header is pointed by point p.
         * @param p Point in tabsPane local system
         * @return Index of pointed tab or -1 if no tab is pointed
         */
        public int getTabIndexAt(Point p) {
            if (isEmpty())
                return -1;
            
            int dx = getWidth()/tabs.size();
            return (int)(Math.floor((double)p.getX()/(double)dx));
        }

        @Override
        public void paintComponent(Graphics g){
//            System.out.println("MyTabPaneHeader.paintComponet(), tabs.size()="+tabs.size());
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D)g;
            double h = getHeight();
            if (tabs.size()>0) {
                double dx = (double)getWidth()/(double)tabs.size();
                for(int i=0; i<tabs.size(); i++) {
                    if (i==actualTabIndex) {
                        g2d.setColor(new Color(.2f, 1.f, .2f));
                        g2d.fill(new Rectangle2D.Double(i*dx, 0, dx-2, h-1));
                        g2d.setColor(new Color(.0f, .5f, .0f));
                        g2d.draw( new Rectangle2D.Double(i*dx, 0, dx-2, h-1));
                    } else {
                        g2d.setColor(Color.BLUE);
                        g2d.draw( new Rectangle2D.Double(i*dx, 0, dx-2, h-1));
                    }
                    String name = getTabName(i);
                    if (name==null) name = "No name";
                    g2d.setColor(Color.BLUE);
                    g2d.drawString(name, (int)(i*dx+2), (int)(h-5));
                }
            } else {
                g2d.setColor(Color.GREEN);
                g2d.draw( new Rectangle2D.Double(0, 0,getWidth()-1, getHeight()-1));
                g2d.drawString("_empty_",0,getHeight()-5);
            }
//            System.out.println(" MyTabPaneHeader.paintComponent(), list.size()="+tabs.size());
        }

        @Override
        public String toString() {
            return "Header of TabsPane[aname: "+TabsPane.this.getAccessibleContext().getAccessibleName()+"], parent is "+getParent();
        }

        
        ////////////////////////////////////////////////////////////////////////
        //               M O U S E   L I S T E N E R S   S T U F F            //
        ////////////////////////////////////////////////////////////////////////

        public void mouseClicked(MouseEvent e) {}
        public void mousePressed(MouseEvent e) {
            int index = getTabIndexAt(e.getPoint());
//            if (index != -1)
//                tabs.get(index).requestFocus();
            displayTab(index);
            requestFocus();
        }
        public void mouseReleased(MouseEvent e) {}
        public void mouseEntered(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {
            mouseOverIndex = -1;
            repaint();
        }

        public void mouseDragged(MouseEvent e) {}
        public void mouseMoved(MouseEvent e) {
            mouseOverIndex = getTabIndexAt(e.getPoint());
            repaint();
        }

        
        ////////////////////////////////////////////////////////////////////////
        //               D R A G   A N D   D R O P   U T I L S                //
        ////////////////////////////////////////////////////////////////////////

        /**
         * Calculates rectagles that take active part in MyGuiDrag and Drop
         * mechanism. Other ares than those are transparent for my gui manager
         * which makes TabsPane parent and ancestors e visible to the MyGuiManager.
         * 
         * @return Array of rectangles which are active drag and drop TabsPane areas.
         *         Rectangles coordinates are specified in TabsPane local system.
         */
        private Rectangle[] getActiveDnDAreas() {
            Rectangle[] rects = new Rectangle[2];

            // Header area rectangle
            int topSpace = 5;
            rects[0] = new Rectangle(0,topSpace, getWidth(), headerHegiht-topSpace);

            // Body area rectangle
            double  spaceH = .75, // Percentage horizontal center space width
                    spaceV = .75; // Precentage vertical center space height

            int x = (int)(TabsPane.this.getWidth() * (1-spaceH))/2,
                y = (int)(TabsPane.this.getHeight()*(1-spaceV))/2,
                w = (int)(TabsPane.this.getWidth() * spaceH),
                h = (int)((TabsPane.this.getHeight()-headerHegiht) * spaceV);

            rects[1] = new Rectangle(x, y, w, h);

            return rects;
        }

        
        ////////////////////////////////////////////////////////////////////////
        //                     D R A G   A N D   D R O P                      //
        ////////////////////////////////////////////////////////////////////////

        public void registerGuiManager(MyGuiManager manager) {
            addMouseMotionListener(manager);
            addMouseListener(manager);
            //manager.registerDnDReady(this);
        }

        public void dndRemove(Object o) {
            TabsPane.this.remove((JComponent)o);
        }

        public JComponent getObjectToPickAt(Point p) {
            int index = getTabIndexAt(p);
            
            if (!isEmpty() && index!=-1)
                return tabs.get(index);
            else
                return null;
        }

        public boolean isValidDropLocation(Point p) {
//            System.out.println("MyTabPaneHeader.isValidDropLocation(), p: "+p+" | this: "+this);
            for(Rectangle r : getActiveDnDAreas()) {
                if (r.contains(p))
                    return true;
            }

            return false;
        }

        public boolean accepts(Object o) {
            if (o instanceof JComponent)
                return true;
            else
                return false;
        }

        public boolean dropObjectAt(Object o, Point p) {
            if ( !(o instanceof JComponent) )
                return false;

            try {
                Component droppedComp = (Component) o;

                if (!isEmpty())
                    TabsPane.this.add(droppedComp, getTabIndexAt(p)+1);
                else
                    TabsPane.this.add(droppedComp);

                return true;

            } catch(Exception e) {
                e.printStackTrace();
                return false;
            }
        }

        public Point getDnDLocationOnScreen() {
            return TabsPane.this.getLocationOnScreen();
        }

        public Rectangle getDnDBounds() {
            return TabsPane.this.getBounds();
        }

        public MyGuiDnDDrawable getFutureLayoutDrawable(Point p) {
            return new FutureLayoutDrawable();
        }

        private class FutureLayoutDrawable implements MyGuiDnDDrawable {

            public Rectangle rect;     // Rectangle in screen coordinates system

            public FutureLayoutDrawable() {
                rect = new Rectangle(getDnDLocationOnScreen(), getDnDBounds().getSize());
            }

            public void paintDrawabe(Graphics g, Component comp) {
                Graphics2D g2d = (Graphics2D) g;
                Point lP = rect.getLocation();
                SwingUtilities.convertPointFromScreen(lP, comp);
                g2d.setColor(Color.ORANGE);

                g2d.draw(new Rectangle(lP, rect.getSize()));
                g2d.draw(new Rectangle(lP.x+1, lP.y+1, (int)rect.getSize().getWidth()-2, (int)rect.getSize().getHeight()-2));
            }
        }
    }
}
